#ifndef __MONITOR_APARATOS_HPP__
#define __MONITOR_APARATOS_HPP__

#include <functional>
#include "common.hpp"

#ifndef MAX_APARATOS
#define MAX_APARATOS 100
#endif

static const aparato_id NIL = 0;

enum eAparato {
	INDEFINIDO = 0,
	BANCO_INCLINADO, BANCO_REGULABLE, BANCO_ROMANO, BICICLETA_SPINNING, CAMILLA, PATADA_DE_BURRO, POLEA_ALTA_Y_BAJA, PRENSA_45, BANCO_STEP
};

enum eRutina {
	INDEFINIDA = 0,
	ABDOMINALES = 1, SENTADILLAS, LAGARTIJAS, PESAS_FUERZA, PESAS_REPETICION,
	ERUTINAS_SIZE,
	SALIR
};

typedef struct {
	aparato_id id;
	enum eAparato tipo;
	cliente_id usuario;
	entrenador_id entrenador;
	size_t usos;
	bool invitado;
} sAparato;


// Funcion de utilidad para el metodo for_each. Debe devolver false para terminar el bucle.
class procesar_aparato {
	protected:
		virtual bool _do (sAparato& aparato) = 0;
	public:
		inline bool operator() (sAparato& aparato) {
			return _do(aparato);
		}
};

typedef struct {
	sAparato item[MAX_APARATOS];
	size_t ultimo_cambio;
} sAparatos;

template <class OSTREAM>
class mostrar_aparato : public procesar_aparato {
	protected:
		OSTREAM& os;
		std::string preambulo;

		virtual bool _do (sAparato& aparato) {
			os << preambulo << std::setw(3) << 1+aparato.id << "," << aparato.tipo << "," << aparato.usuario << "," << aparato.entrenador << '\n';
			return true;
		}
	public:
		mostrar_aparato (OSTREAM& ostream, const std::string& preambulo) : os(ostream) {
			this->preambulo = preambulo;
		}
}; // class mostrar_aparato

template <class APARATOS, class SEMAPHORE> 
class monitor_aparatos_base {
	protected:
		APARATOS aparatos;
		SEMAPHORE mutex;		
		bool inicializado;

		monitor_aparatos_base (const monitor_aparatos_base&);
		const monitor_aparatos_base& operator= (const monitor_aparatos_base&);
	public:
		monitor_aparatos_base (key_t aparatos_key, key_t mutex_key, size_t mutex_num)
			 : aparatos(aparatos_key), mutex(mutex_key,mutex_num) {			
			inicializado = false;
		}

		monitor_aparatos_base (key_t aparatos_key, const SEMAPHORE& sem)
			 : aparatos(aparatos_key), mutex(sem) {			
			inicializado = false;
		}
		
		inline int get(int mode) {
			int error = mutex.get(mode);

			if (error == 0)
				error = aparatos.get(mode);
			inicializado = (error == 0);
			return error;
		}

		inline bool init( std::vector<enum eAparato> tipos) {
			bool exito = false;
			
			if (inicializado) {
				mutex.init(1);
				mutex.wait();
					for (size_t i = 0; i<MAX_APARATOS; ++i) {
						aparatos->item[i].id = i+1;
						aparatos->item[i].tipo = (i<tipos.size()) ? tipos[i] : INDEFINIDO;
						aparatos->item[i].usuario = 0;
						aparatos->item[i].entrenador = 0;
						aparatos->item[i].usos = 0;
						aparatos->item[i].invitado = false;
					}
					aparatos->ultimo_cambio = time(NULL);
				mutex.signal();
			}

			return exito;
		}

		inline bool ocupar_aparato(aparato_id id, const cliente_id& cliente, bool invitado, const entrenador_id& entrenador) {
			bool exito = false;
			
			if ( (inicializado) && (id>0) && (id <= MAX_APARATOS) && (cliente>0) && (entrenador>0) ) {
				id--; // Corro el identificador a su posición en el vector de aparatos.
				mutex.wait();
					if (aparatos->item[id].usuario == 0) {
						aparatos->item[id].usuario = cliente;
						//aparatos->item[id].entrenador = entrenador;
						aparatos->item[id].usos++;
						aparatos->item[id].invitado = invitado;
						aparatos->ultimo_cambio = time(NULL);
						exito = true;
					}
				mutex.signal();
			}
			return exito;
		}

		inline bool liberar_aparato(aparato_id id) {
			bool exito = false;

			if ( (inicializado) && (id>0) && (id <= MAX_APARATOS) ) {
				id--; // Corro el identificador a su posición en el vector de aparatos.
				mutex.wait();
					if (aparatos->item[id].usuario > 0) {
						aparatos->item[id].usuario = 0;
						aparatos->item[id].entrenador = 0;
						aparatos->item[id].invitado = false;
						aparatos->ultimo_cambio = time(NULL);
						exito = true;
					}
				mutex.signal();
			}
			return exito;
		}

		inline void for_each_aparato ( procesar_aparato& procesar ) {
			if (inicializado) {
				mutex.wait();
					for (aparato_id id=0; id<MAX_APARATOS; ++id) {
						if (! procesar(aparatos->item[id]) ) break;
					}
				mutex.signal();
			}
		}

		inline size_t ultimo_cambio () {
			size_t ultimo_cambio = 0;
			if (inicializado) {
				mutex.wait();
					ultimo_cambio = aparatos->ultimo_cambio;
				mutex.signal();
			}
			return ultimo_cambio;
		}

		inline int destroy() {
			int error = aparatos.destroy();
			error+= mutex.destroy();
			return error;
		}

		virtual ~monitor_aparatos_base () {}
}; // class monitor_aparatos_base

typedef monitor_aparatos_base< ipc::shared_memory<sAparatos>,
						 ipc::semaphore
> monitor_aparatos;

typedef monitor_aparatos_base< ipc::shared_memory_with_exceptions<sAparatos>,
						 ipc::semaphore_with_exceptions
> monitor_aparatos_with_exceptions;

#endif /* __MONITOR_APARATOS_HPP__ */

